/*
 * Do not forget to test your class!!!
 *
 * Be sure to test ALL methods and all different cases of
 * the methods (the only exception allowed is overallLetterGrade)
 * For example, in addition to checking to be sure all normal cases
 * working correctly, be sure to test exception cases in 
 * addHomework so that method will not create an indexOutOfBounds
 * error.  Be sure your changeHomework method does not change the
 * homework grade if n is past where you have already entered.  Check
 * to make sure your getHomeworkGrade will return -1 if n is past
 * where have already entered.
 * Do not forget to avoid integer division when calculating averages!
 * Be sure at least 1 of your averages has a decimal!
 *
 * Remember to calculate expected results!
 * Display Expected = '  '  then calculate your answer and display
 * your answer to be sure it matches!
 */
import java.util.ArrayList;

public class JavaGradebook {
	public static void main(String[] args) {

		// Tester for FINAL JAVA GRADE CLASS

		JavaGradebook currentGrade = new JavaGradebook(); // PRIMARY
															// CONSTRUCTOR

		// Adding five homework grades
		int score = 0;
		currentGrade.addHomework(5, 6);
		currentGrade.addHomework(7, 9);
		currentGrade.addHomework(3, 5);
		currentGrade.addHomework(5, 5);
		currentGrade.addHomework(11, 15);
		// Calculating homework average...
		currentGrade.homeworkAverage();
		if (currentGrade.homeworkAverage() > 77.49
				&& currentGrade.homeworkAverage() < 77.51)
			score++;
		else
			System.out.println("error in homeworkAverage");
		// Now changing two homework grades...
		currentGrade.changeHomework(16, 19, 5);
		currentGrade.changeHomework(9, 9, 3);
		// Calculating the NEW homework average...
		currentGrade.homeworkAverage();
		if (currentGrade.homeworkAverage() > 87.49
				&& currentGrade.homeworkAverage() < 87.51)
			score++;
		else
			System.out.println("error in changeHomework");
		// Getting two hommework grades
		// System.out.println(currentGrade.getHomeworkGrade(3));
		if (currentGrade.getHomeworkGrade(4) == 100d)
			score++;
		else
			System.out.println("error in getHomeworkGrade"
					+ currentGrade.getHomeworkGrade(6));
		// currentGrade.getHomeworkGrade(5);
		if (currentGrade.getHomeworkGrade(5) > 84
				&& currentGrade.getHomeworkGrade(5) < 85)
			score++;
		else
			System.out.println("error in getHomeworkGrades"
					+ currentGrade.getHomeworkGrade(5));

		currentGrade.addTest(42, 45);
		currentGrade.addTest(41, 45);
		currentGrade.addTest(54, 60);
		currentGrade.addTest(46, 50);
		currentGrade.testAverage();
		if (currentGrade.testAverage() > 91.49
				&& currentGrade.testAverage() < 91.51)
			score++;
		else
			System.out.println("error in testAverage");

		currentGrade.changeTest(47, 45, 2);
		currentGrade.changeTest(38, 45, 1);
		currentGrade.changeTest(49, 50, 4);
		currentGrade.testAverage();
		if (currentGrade.testAverage() > 93.99
				&& currentGrade.testAverage() < 94.01)
			score++;
		else
			System.out.println("error in changeTest");
		if (currentGrade.getTestGrade(3) == 90)
			score++;
		else
			System.out.println("error in getTest");

		if (currentGrade.overallGrade() > 92
				&& currentGrade.overallGrade() < 92.5)
			score++;
		else
			System.out.println("error in overallGrade");

		currentGrade.overallLetterGrade();
		if (currentGrade.overallLetterGrade().equalsIgnoreCase("A"))
			score++;
		else
			System.out.println("error in overallLetterGrade");

		System.out.println(currentGrade);

		System.out.println("score = " + score);

		additionalTests();

	}

	public static void additionalTests() {
		int score = 0;
		JavaGradebook myGrade = new JavaGradebook();
		myGrade.addHomework(5, 6);
		myGrade.addHomework(7, 9);
		myGrade.addHomework(3, 5);
		myGrade.addHomework(5, 5);
		myGrade.addHomework(11, 15);
		if (myGrade.ignoreLowestHomework() == 4
				|| myGrade.ignoreLowestHomework() == 2)
			score++;
		else
			System.out.println("error in ignoreLowestHomework"
					+ myGrade.ignoreLowestHomework());
		if (myGrade.homeworkAverageIngoreN(2) == 80)
			score++;
		else
			System.out.println("error in homeworkAverageIgnoreN");

		myGrade.addTest(54, 60);
		myGrade.addTest(57, 60);
		myGrade.addTest(63, 70);
		myGrade.addTest(75, 80);
		System.out.println(myGrade.findMaxTestGrade());
		if (myGrade.findMaxTestGrade() == 1)
			score++;
		else
			System.out.println("error in findMaxTestGrade");
		if (myGrade.ignoreLowestTest() == 2)
			score++;
		else
			System.out.println("error in ignoreLowestTest");
		if (myGrade.testAverageIngoreN(1) > 91.4
				&& myGrade.testAverageIngoreN(1) < 91.5)
			score++;
		else
			System.out.println("error in testAverageIgnoreN");
		if (myGrade.overallGradeRemoveMins() > 82.59
				&& myGrade.overallGradeRemoveMins() < 82.61)
			score++;
		else
			System.out.println("error in overallGradeRemoveMins"+myGrade.overallGradeRemoveMins());
		if (myGrade.overallGrade() > 88.53 && myGrade.overallGrade() < 88.58)
			score++;
		else
			System.out.println("error in overallGrade extra");

		JavaGradebook test = new JavaGradebook();

		test.addHomework(10, 10);
		test.addHomework(25, 35);
		test.addHomework(1, 2);
		test.addHomework(53, 53);

		if (test.ignoreLowestHomework() == 1)
			score++;
		else
			System.out.println("error in ignoreLowestHomework Extra Test");

		System.out.println("additional score = " + score);

	}

	/*
	 * Constructor initializes Array to hold up to 10 homework and test grades.
	 */
	public JavaGradebook() {
		test = new ArrayList<WeightedGrade>();
		homework = new ArrayList<WeightedGrade>();
	}

	/*
	 * Adds a homework grade to the array. If there is no more room in the array
	 * do not add the grade to the array. Return true if the grade is
	 * successfully added, return false if the grade is not added.
	 */
	public boolean addHomework(int newHomeworkGrade, int p) {
		return homework.add(new WeightedGrade(newHomeworkGrade, p));
	}

	public boolean addHomework(int newHomeworkGrade) {
		return addHomework(newHomeworkGrade, 100);
	}

	/*
	 * Changes a grade of an already entered homework. Recall n is the number of
	 * the homework - begins at 1! If the number of the homework has not yet
	 * been entered do not change the grade. Do not allow an indexOutOfBounds
	 * erorr. If n is too small or too big, do not change the grade. Return true
	 * if the grade is successfully added, return false if the grade is not
	 * added.
	 */
	public boolean changeHomework(int changeHomeworkGrade, int n) {
		if (isInRange(n, true)) {
			homework.get(n - 1).setPointsEarned(changeHomeworkGrade);
			return true;
		}
		return false;
	}

	/*
	 * Changes a grade of an already entered homework. Recall n is the number of
	 * the homework - begins at 1! If the number of the homework has not yet
	 * been entered do not change the grade. Do not allow an indexOutOfBounds
	 * erorr. If n is too small or too big, do not change the grade. Return true
	 * if the grade is successfully added, return false if the grade is not
	 * added.
	 */
	public boolean changeHomework(int changeHomeworkGrade, int p, int n) {
		if (isInRange(n, true)) {
			homework.get(n - 1).setPointsEarned(changeHomeworkGrade);
			homework.get(n - 1).setPointsPossible(p);
			return true;
		}
		return false;
	}

	/*
	 * Computes the homework average of only the entered homework grades
	 */
	public double homeworkAverage() {
		return homeworkAverageIngoreN(-1);
	}

	/*
	 * Returns the grade of the nth homework. Recall where the first homework is
	 * stored in the array! If the grade at the nth position has not yet been
	 * entered, return -1. Do not allow an indexOutOfBounds erorr. If n is too
	 * small or too big, do not change the grade and return -1.
	 */
	public double getHomeworkGrade(int n) {
		if (isInRange(n, true))
			return homework.get(n - 1).getPercent() * 100;
		return -1;
	}

	/*
	 * All test methods work the same way as homework methods. Copy, paste and
	 * adjust where needed!
	 */
	public boolean addTest(int newTestGrade) {
		return addTest(newTestGrade, 100);
	}

	/*
	 * All test methods work the same way as homework methods. Copy, paste and
	 * adjust where needed!
	 */
	public boolean addTest(int newTestGrade, int p) {
		return test.add(new WeightedGrade(newTestGrade, p));
	}

	public double testAverage() {
		return testAverageIngoreN(-1);
	}

	public boolean changeTest(int changeTestGrade, int n) {
		if (isInRange(n, false)) {
			test.get(n - 1).setPointsEarned(n);
			return true;
		}
		return false;
	}

	public boolean changeTest(int e, int p, int n) {
		if (isInRange(n, false)) {
			test.get(n - 1).setPointsEarned(e);
			test.get(n - 1).setPointsPossible(p);
			return true;
		}
		return false;
	}

	public double getTestGrade(int n) {
		if (isInRange(n, false))
			return test.get(n - 1).getPercent() * 100;
		return -1;
	}

	/*
	 * Returns the weighted average of 25% homework, 75% tests, Make use of
	 * other methods! Do not repeat code!
	 */
	public double overallGrade() {
		return testAverage() * .75 + homeworkAverage() * .25;
	}

	/*
	 * Returns a String of your overall letter grade of A, B, C, D or F based on
	 * the Neuqua grading scale. Again, Do NOT repeat code!
	 */
	public String overallLetterGrade() {
		double numGrade = overallGrade();
		// Put your code here
		if (numGrade >= 100)
			return "A+";
		if (numGrade >= 91)
			return "A";
		if (numGrade >= 81)
			return "B";
		if (numGrade >= 71)
			return "C";
		if (numGrade >= 65)
			return "D";
		if (numGrade >= 0)
			return "F";
		return ("You did real bad.");
	}

	/*
	 * Returns a String of your overall letter grade of A, B, C, D or F based on
	 * the Neuqua grading scale. Example format: JavaGrade = A Example format 2:
	 * JavaGrade = C
	 */
	public String toString() {
		return "JavaGrade = " + overallLetterGrade();
	}

	/*
	 * Returns true if n is in range of the Homework or Test array, otherwise
	 * returns false hOrT false = Homework array hOrT true = Test array Note:
	 * This method is private and cannot be tested outside of the class.
	 */
	private boolean isInRange(int n, boolean hOrT) {
		return n >= 1 && n <= (hOrT ? homework.size() : test.size());
	}

	// Do NOT modify instance fields! You cannot change the design of this code!
	private ArrayList<WeightedGrade> homework;
	private ArrayList<WeightedGrade> test;

	/*
	 * Precondition: none Postcondition: Returns the index of the maximum test
	 * grade.
	 */
	public int findMaxTestGrade() {
		int max = 0;
		for (int i = 0; i < test.size(); i++) {
			if (test.get(i).getPercent() > test.get(max).getPercent())
				max = i;
		}
		return max;
	}

	/*
	 * Precondition: none Postcondition: Returns the index of the minimum test
	 * grade.
	 */
	public int findMinTestGrade() {
		int min = 0;
		for (int i = 0; i < test.size(); i++) {
			if (test.get(i).getPercent() < test.get(min).getPercent())
				min = i;
		}
		return min;
	}

	/*
	 * Precondition: none Postcondition: Returns a modified overall grade not
	 * counting the one grade that lowers your grade the most from each
	 * category. Use the private helper methods to help you achieve this. Hint:
	 * Work your way from the bottom then back to this method Efficiency is not
	 * important when coding your methods. It is fine to use brute force (try
	 * all possibilities).
	 * 
	 * Note: Mr. Goldman will NOT do this for you when computing your grade
	 * 
	 * YOU CANNOT MODIFY YOUR INSTANCE FIELDS IN ANY WAY WHILE COMPUTING ANY OF
	 * THESE METHODS
	 */
	public double overallGradeRemoveMins() {
		return homeworkAverageIngoreN(ignoreLowestHomework()) * .8 + testAverageIngoreN(ignoreLowestTest()) * .2;
	}

	/*
	 * Precondition: At least two WeightedGrade objects are in homework
	 * Postcondition: Returns the index of the homework grade that lowers your
	 * homeworkAverage the most. Note: This is NOT necessarily the single lowest
	 * WeightedGrade. For example, a 1/4 is only a 25% but may not be lowering
	 * your grade as much as a 5/15. Make use of your homeworkAverageIngoreN(int
	 * n) helper method.
	 */
	private int ignoreLowestHomework() {
		double best = 0;
		int bi = -1;
		for (int i = 0; i < homework.size(); i++) {
			if (homeworkAverageIngoreN(i) > best) {
				best = homeworkAverageIngoreN(i);
				bi = i;
			}
		}
		return bi;
	}

	/*
	 * Precondition: At least two WeightedGrade objects are in homework
	 * Postcondition: Returns the average of the homework grade ignoring the
	 * WeightedGrade object at index n.
	 */
	private double homeworkAverageIngoreN(int n) {
		double sume = 0;
		double sump = 0;
		for (int i = 0; i < homework.size(); i++) {
			if (i == n)
				continue;
			WeightedGrade d = homework.get(i);
			sume += d.getPointsEarned();
			sump += d.getPointsPossible();
		}
		return sume / sump * 100;
	}

	/*
	 * Precondition: At least two WeightedGrade objects are in test
	 * Postcondition: Returns the index of the test grade that lowers your
	 * testAverage the most. Note: This is NOT necessarily the single lowest
	 * WeightedGrade. For example, a 1/4 is only a 25% but may not be lowering
	 * your grade as much as a 5/15. Make use of your testAverageIngoreN(int n)
	 * helper method.
	 */
	private int ignoreLowestTest() {
		double best = 0;
		int bi = -1;
		for (int i = 0; i < homework.size(); i++) {
			if (testAverageIngoreN(i) > best) {
				best = testAverageIngoreN(i);
				bi = i;
			}
		}
		return bi;
	}

	/*
	 * Precondition: At least two WeightedGrade objects are in homework
	 * Postcondition: Returns the average of the homework grade ignoring the
	 * WeightedGrade object at index n. Use thi
	 */
	private double testAverageIngoreN(int n) {
		double sume = 0;
		double sump = 0;
		for (int i = 0; i < test.size(); i++) {
			if (i == n)
				continue;
			WeightedGrade d = test.get(i);
			sume += d.getPointsEarned();
			sump += d.getPointsPossible();
		}
		return sume / sump * 100;
	}
}
